using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Threading;

using DotUpdater;

using Microsoft.ApplicationBlocks.Updater;
//using Microsoft.ApplicationBlocks.Updater.Configuration;
//using Microsoft.Practices.EnterpriseLibrary.Configuration;

namespace Katersoft.Downloaders
{
	/// <summary>
	/// Implements a HTTP downloader for the updater application block V2.0.
	/// </summary>
	/// <remarks>
	/// The <c>HttpDownloader</c> class can be used to download application updates via HTTP. It supports synchronous and asynchronous
	/// operation as well as progress reporting. In addition, the progress reporting is not at the file level, it is at the byte level.
	/// </remarks>
	public sealed class HttpDownloader : IDownloader
	{
		/// <summary>
		/// The thread to perform the download.
		/// </summary>
		private Thread _downloaderThread;

		/// <summary>
		/// Contains configuration settings for the HTTP downloader.
		/// </summary>
		private HttpDownloaderProviderData _configuration = new HttpDownloaderProviderData();

		/// <summary>
		/// The configuration name for this downloader.
		/// </summary>
		private const string CONFIGURATION_NAME = "downloader";

		/// <summary>
		/// The download provider name.
		/// </summary>
		private const string DOWNLOAD_PROVIDER_NAME = "HTTPDownloader";

		/// <summary>
		/// A synchronisation object.
		/// </summary>
		private readonly object LOCK = new object();

		/// <summary>
		/// Gets the configuration name for the HTTP downloader.
		/// </summary>
		public static string ConfigurationName
		{
			get
			{
				return CONFIGURATION_NAME;
			}
//			set
//			{
//				//nothing to do
//			}
		}
		
//		/// <summary>
//		/// Initialises the HTTP downloader.
//		/// </summary>
//		public void Initialize()
//		{
//			//UpdaterConfigurationView updaterConfigurationView = (UpdaterConfigurationView) configurationView;
//			//_configuration = (HttpDownloaderProviderData) updaterConfigurationView.GetDownloadProviderData(DOWNLOAD_PROVIDER_NAME);
//            //_configuration = new HttpDownloaderProviderData();
//		}

		/// <summary>
		/// Downloads the specified task synchronously via HTTP. If <paramref name="maxWaitTime"/> is surpassed prior to the download
		/// completing, a download error is raised.
		/// </summary>
        [SuppressMessage("Microsoft.Design", "CA1031")]
		public void Download(UpdaterTask task, TimeSpan maxWaitTime)
		{
			try
			{
				OnDownloadStarted(new TaskEventArgs(task));
				//this object is used to perform the downloading on a separate thread
				AsyncDownloader downloader = new AsyncDownloader(task, _configuration, 
                    OnDownloadProgress, 
                    OnDownloadTotalSizeCalculationStarted, 
                    //new OnDownloadTotalSizeCalculationProgressEventHandler(OnDownloadTotalSizeCalculationProgress), 
                    OnDownloadTotalSizeCalculationCompleted);
				CreateDownloaderThread(downloader);
				_downloaderThread.Start();
				DateTime endTime = DateTime.Now + maxWaitTime;

				while ((endTime > DateTime.Now) && !downloader.IsComplete)
				{
					Thread.Sleep(1000);
				}

				if (!downloader.IsComplete)
				{
					//abort the thread if it didn't complete
					_downloaderThread.Abort();
					throw new WebException("Download surpassed time out of " + maxWaitTime);
				}
				
                if (downloader.Exception != null)
				{
					//raise the error event if the downloader thread erred out
					OnDownloadError(new DownloadTaskErrorEventArgs(task, downloader.Exception));
				}
				else
				{
					//if we get here then it's all good
					OnDownloadCompleted(new TaskEventArgs(task));
				}
			}
			catch (Exception e)
			{
				OnDownloadError(new DownloadTaskErrorEventArgs(task, e));
			}
		}

		/// <summary>
		/// Downloads the specified task asynchronously via HTTP.
		/// </summary>
		public void BeginDownload(UpdaterTask task)
		{
            Thread t =  new Thread(() => Download(task, TimeSpan.FromMinutes(30)));
		    t.IsBackground = true;
		    t.Start();
		}
		
//		public void BeginDownload(UpdaterTask task)
//		{
//			try
//			{
//				OnDownloadStarted(new TaskEventArgs(task));
//				//this object is used to perform the downloading on a separate thread
//				AsyncDownloader downloader = new AsyncDownloader(task, _configuration, 
//                    (OnDownloadProgress), 
//                    (OnDownloadTotalSizeCalculationStarted), 
//                    //(OnDownloadTotalSizeCalculationProgress), 
//                    (OnDownloadTotalSizeCalculationCompleted));
//				CreateDownloaderThread(downloader);
//				_downloaderThread.Start();
//			}
//			catch (Exception e)
//			{
//				OnDownloadError(new DownloadTaskErrorEventArgs(task, e));
//			}
//		}

		/// <summary>
		/// Cancels an asynchronous HTTP download operation.
		/// </summary>
		public bool CancelDownload(UpdaterTask task)
		{
			_downloaderThread.Abort();
			return true;
		}

		/// <summary>
		/// Creates the downloader thread.
		/// </summary>
		private void CreateDownloaderThread(AsyncDownloader downloader)
		{
			_downloaderThread = new Thread(downloader.Download);
			_downloaderThread.Name = "Downloader";
			_downloaderThread.IsBackground = true;
		}

		/// <summary>
		/// Delegate for handling the <see cref="DownloadTotalSizeCalculationStarted"/> event.
		/// </summary>
		//public delegate void DownloadTotalSizeCalculationStartedEventHandler(object sender, TaskEventArgs e);

//		/// <summary>
//		/// Delegate for handling the <see cref="DownloadTotalSizeCalculationCompleted"/> event.
//		/// </summary>
//		public delegate void DownloadTotalSizeCalculationProgressEventHandler(object sender, DownloadTotalSizeCalculationProgressEventArgs e);

		/// <summary>
		/// Delegate for handling the <see cref="DownloadTotalSizeCalculationCompleted"/> event.
		/// </summary>
		//internal delegate void DownloadTotalSizeCalculationCompletedEventHandler(object sender, TaskEventArgs e);

		/// <summary>
		/// Fired when the HTTP downloader begins calculating file sizes for the files to be downloaded.
		/// </summary>
		/// <remarks>
		/// This event has to be <c>static</c> because the client is unable to obtain a reference to the <c>HttpDownloader</c> instance (updater design flaw).
		/// </remarks>
        internal static event EventHandler<TaskEventArgs> DownloadTotalSizeCalculationStarted;

		/// <summary>
		/// Fired when the HTTP downloader has progress information for file size calculations.
		/// </summary>
		/// <remarks>
		/// This event has to be <c>static</c> because the client is unable to obtain a reference to the <c>HttpDownloader</c> instance (updater design flaw).
		/// </remarks>
		//public static event DownloadTotalSizeCalculationProgressEventHandler DownloadTotalSizeCalculationProgress;

		/// <summary>
		/// Fires when the HTTP downloader has finished calculating file sizes.
		/// </summary>
		/// <remarks>
		/// This event has to be <c>static</c> because the client is unable to obtain a reference to the <c>HttpDownloader</c> instance (updater design flaw).
		/// </remarks>
        public static event EventHandler<TaskEventArgs> DownloadTotalSizeCalculationCompleted;

		/// <summary>
		/// Fired when the HTTP downloader begins downloading files.
		/// </summary>
        public event EventHandler<TaskEventArgs> DownloadStarted;

		/// <summary>
		/// Fired whenever the HTTP downloader has progress information to report about the current downloads.
		/// </summary>
        public event EventHandler<DownloadTaskProgressEventArgs> DownloadProgress;

		/// <summary>
		/// Fired when the HTTP downloader has finished downloading.
		/// </summary>
        public event EventHandler<TaskEventArgs> DownloadCompleted;

		/// <summary>
		/// Fired when an error occurs in the HTTP downloader whilst attempting to download updates.
		/// </summary>
        public event EventHandler<DownloadTaskErrorEventArgs> DownloadError;

	    #region IDownloader Members

	    public Logger logger { get; set; }

	    #endregion

	    /// <summary>
		/// Used to invoke the <see cref="OnDownloadTotalSizeCalculationStarted"/> method.
		/// </summary>
		internal delegate void OnDownloadTotalSizeCalculationStartedEventHandler(TaskEventArgs e);

		/// <summary>
		/// Fires the <see cref="DownloadTotalSizeCalculationStarted"/> method.
		/// </summary>
		private void OnDownloadTotalSizeCalculationStarted(TaskEventArgs e)
		{
			if (DownloadTotalSizeCalculationStarted != null)
			{
				DownloadTotalSizeCalculationStarted(this, e);
			}
		}

//		/// <summary>
//		/// Used to invoke the <see cref="OnDownloadTotalSizeCalculationProgress"/> method.
//		/// </summary>
//		internal delegate void OnDownloadTotalSizeCalculationProgressEventHandler(DownloadTotalSizeCalculationProgressEventArgs e);
//
//		/// <summary>
//		/// Fires the <see cref="DownloadTotalSizeCalculationProgress"/> method.
//		/// </summary>
//		private void OnDownloadTotalSizeCalculationProgress(DownloadTotalSizeCalculationProgressEventArgs e)
//		{
//			if (DownloadTotalSizeCalculationProgress != null)
//			{
//				DownloadTotalSizeCalculationProgress(this, e);
//			}
//		}

		/// <summary>
		/// Used to invoke the <see cref="OnDownloadTotalSizeCalculationCompleted"/> method.
		/// </summary>
		internal delegate void OnDownloadTotalSizeCalculationCompletedEventHandler(TaskEventArgs e);

		/// <summary>
		/// Fires the <see cref="DownloadTotalSizeCalculationCompleted"/> method.
		/// </summary>
		private void OnDownloadTotalSizeCalculationCompleted(TaskEventArgs e)
		{
			if (DownloadTotalSizeCalculationCompleted != null)
			{
				DownloadTotalSizeCalculationCompleted(this, e);
			}
		}
  
		/// <summary>
		/// Fires the <see cref="DownloadStarted"/> method.
		/// </summary>
		private void OnDownloadStarted(TaskEventArgs e)
		{
			if (DownloadStarted != null)
			{
				DownloadStarted(this, e);
			}
		}
 
		/// <summary>
		/// Used to invoke the <see cref="OnDownloadProgress"/> method.
		/// </summary>
		internal delegate void OnDownloadProgressEventHandler(DownloadTaskProgressEventArgs e);

		/// <summary>
		/// Fires the <see cref="DownloadProgress"/> method.
		/// </summary>
		private void OnDownloadProgress(DownloadTaskProgressEventArgs e)
		{
			lock (LOCK)
			{
				if (DownloadProgress != null)
				{
					DownloadProgress(this, e);
				}
			}
		}
    
		/// <summary>
		/// Fires the <see cref="DownloadCompleted"/> method.
		/// </summary>
		private void OnDownloadCompleted(TaskEventArgs e)
		{
			if (DownloadCompleted != null)
			{
				DownloadCompleted(this, e);
			}
		}

		/// <summary>
		/// Fires the <see cref="DownloadError"/> method.
		/// </summary>
		private void OnDownloadError(DownloadTaskErrorEventArgs e)
		{
			if (DownloadError != null)
			{
				DownloadError(this, e);
			}
		}

		/// <summary>
		/// Performs the actual downloading of updated files.
		/// </summary>
		private class AsyncDownloader
		{
			/// <summary>
			/// The task whose files will be downloaded.
			/// </summary>
			private UpdaterTask _task;

			/// <summary>
			/// Contains configuration settings.
			/// </summary>
			private HttpDownloaderProviderData _configuration;

			/// <summary>
			/// Stores the last time a download progress report was issued.
			/// </summary>
			private DateTime _lastProgressReport;

			/// <summary>
			/// The delegate to invoke to report download progress.
			/// </summary>
			private OnDownloadProgressEventHandler _progressDelegate;

			/// <summary>
			/// The delegate to invoke to report that file size calculations have started.
			/// </summary>
			private OnDownloadTotalSizeCalculationStartedEventHandler _totalSizeStarted;

//			/// <summary>
//			/// The delegate to invoke to report file size calculations progress.
//			/// </summary>
//			private OnDownloadTotalSizeCalculationProgressEventHandler _totalSizeProgress;

			/// <summary>
			/// The delegate to invoke to report that file size calculations have completed.
			/// </summary>
			private OnDownloadTotalSizeCalculationCompletedEventHandler _totalSizeCompleted;

			/// <summary>
			/// The buffer used whilst downloading data.
			/// </summary>
			private byte[] _buffer;

			/// <summary>
			/// Set to <c>true</c> if the download completes successfully.
			/// </summary>
			private bool _isComplete;

			/// <summary>
			/// Any exception that occurred during the download.
			/// </summary>
			private Exception _exception;

			/// <summary>
			/// Synchronisation object.
			/// </summary>
			private readonly object LOCK;

			/// <summary>
			/// Gets or sets a value indicating whether the download completed successfully.
			/// </summary>
			internal bool IsComplete
			{
				get
				{
					lock (LOCK)
					{
						return _isComplete;
					}
				}
				set
				{
					lock (LOCK)
					{
						_isComplete = value;
					}
				}
			}

			/// <summary>
			/// Gets or sets an exception that occurred during the download process.
			/// </summary>
			internal Exception Exception
			{
				get
				{
					lock (LOCK)
					{
						return _exception;
					}
				}
				set
				{
					lock (LOCK)
					{
						_exception = value;
					}
				}
			}

			/// <summary>
			/// Constructs an <c>AsyncDownloader</c> instance.
			/// </summary>
			internal AsyncDownloader(UpdaterTask task, 
                HttpDownloaderProviderData configuration, 
                OnDownloadProgressEventHandler progressDelegate, 
                OnDownloadTotalSizeCalculationStartedEventHandler totalSizeStarted, 
                //OnDownloadTotalSizeCalculationProgressEventHandler totalSizeProgress, 
                OnDownloadTotalSizeCalculationCompletedEventHandler totalSizeCompleted)
			{
				LOCK = new object();
				_task = task;
				_configuration = configuration;
				_progressDelegate = progressDelegate;
				_totalSizeStarted = totalSizeStarted;
				//_totalSizeProgress = totalSizeProgress;
				_totalSizeCompleted = totalSizeCompleted;
				_buffer = new byte[_configuration.DownloadBufferSize];
				_lastProgressReport = DateTime.MinValue;
			}

			/// <summary>
			/// Performs the download operation.
			/// </summary>
            [SuppressMessage("Microsoft.Design", "CA1031")]
            internal void Download()
			{
				long totalBytes = 0;
				long transferredBytes = 0;
                IWebProxy webProxy = WebRequest.DefaultWebProxy;

				if (webProxy != null)
				{
					webProxy.Credentials = CredentialCache.DefaultCredentials;
				}

				try
				{
					_totalSizeStarted(new TaskEventArgs(_task));

					//first determine the total content length
					for (int i = 0; i < _task.Files.Length; ++i)
					{
						string file = _task.Files[i];
						string uri = GetUri(file);
						WebRequest webRequest = WebRequest.Create(uri);
						webRequest.Proxy = webProxy;
						WebResponse webResponse = webRequest.GetResponse();
						totalBytes += webResponse.ContentLength;
						webResponse.Close();
//						_totalSizeProgress(new DownloadTotalSizeCalculationProgressEventArgs(
//                            _task.Manifest.Files.Count, i + 1));
					}

					_totalSizeCompleted(new TaskEventArgs(_task));

					//now download each file
					for (int i = 0; i < _task.Files.Length; ++i)
					{
						string file = _task.Files[i];
						string uri = GetUri(file);
						WebRequest webRequest = WebRequest.Create(uri);
						webRequest.Proxy = webProxy;
						WebResponse webResponse = webRequest.GetResponse();
						string outFile = Path.Combine(_task.DownloadFilesBase, file);
						int read = 0;

						//make sure the destination directory exists
						if (!Directory.Exists(Path.GetDirectoryName(outFile)))
						{
							Directory.CreateDirectory(Path.GetDirectoryName(outFile));
						}

						using (Stream responseStream = webResponse.GetResponseStream())
						using (Stream fileStream = new FileStream(outFile, FileMode.Create, FileAccess.Write))
						{
							while ((read = responseStream.Read(_buffer, 0, _buffer.Length)) != 0)
							{
								transferredBytes += read;
								fileStream.Write(_buffer, 0, read);
								TimeSpan timeSinceLastProgressReport = DateTime.Now - _lastProgressReport;

								if ((_configuration.DownloadProgressMaximumFrequency == 0) || (timeSinceLastProgressReport.TotalMilliseconds > _configuration.DownloadProgressMaximumFrequency))
								{
									_lastProgressReport = DateTime.Now;
									_progressDelegate(new DownloadTaskProgressEventArgs(totalBytes, transferredBytes, _task.Files.Length, i, _task));
								}
							}
						}

						webResponse.Close();
					}
				}
				catch (Exception e)
				{
					if (!(e is ThreadAbortException))
					{
						Exception = e;
					}
				}

				//if the thread is aborted, this won't execute because the ThreadAbortException will continue propogating
				IsComplete = true;
			}

			/// <summary>
			/// Obtains a URI for the specified file.
			/// </summary>
			private string GetUri(string  file)
			{
				return string.Format("{0}{1}", _task.RemoteFilesBase, file);
			}
		}
	}
}
